package org.xbl.xchain.sdk;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbl.xchain.sdk.amino.Codec;
import org.xbl.xchain.sdk.tx.StdSignBytesV1;
import org.xbl.xchain.sdk.tx.StdTx;
import org.xbl.xchain.sdk.tx.StdSignBytesV2;
import org.xbl.xchain.sdk.types.Msg;
import org.xbl.xchain.sdk.types.*;
import org.xbl.xchain.sdk.utils.EncodeUtils;

import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;

public class TxBuilder {
    private static final Logger LOGGER = LoggerFactory.getLogger(TxBuilder.class);

    public static List<String> getSignerList(List<? extends Msg> msgs) {
        Set<String> existedSet = new HashSet<>();
        List<String> addrList = new ArrayList<>();
        for (Msg msg : msgs) {
            String[] signers = msg.signers();
            for (int i = 0; i < signers.length; i++) {
                if (!existedSet.contains(signers[i])) {
                    existedSet.add(signers[i]);
                    addrList.add(signers[i]);
                }
            }
        }
        return addrList;
    }

    public static byte[] buildTx(SysConfig config, List<? extends Msg> msgs, List<Account> accounts, TxConfig txConfig) throws Exception {
        List<Signature> signatures = new ArrayList<>();
        List<MsgTypeValue> unsignedMsgs = msgs.stream().map(Msg::transferMsgTypeValue).collect(Collectors.toList());
        List<String> signerList = getSignerList(msgs);
        byte[] nonce = null;
        if (Version.NONCE.equals(config.getVersion())) {
            nonce = generateNonce();
        }
        for (int i = 0; i < accounts.size(); i++) {
            Account account = accounts.get(i);
            if (!signerList.get(i).equals(accounts.get(i).getKeyInfo().getAddress())) {
                throw new Exception("signer and keyinfo address not match");
            }
            String signDataJson = null;
            if (Version.SEQUENCE.equals(config.getVersion())) {
                StdSignBytesV1 stdSignBytesV1 = new StdSignBytesV1(account.getAuthAccount().getValue().getAccNum(), account.getAuthAccount().getValue().getSequence(), config.getChainId(), unsignedMsgs, txConfig);
                signDataJson = EncodeUtils.toJsonStringSortKeys(stdSignBytesV1);
            } else if (Version.NONCE.equals(config.getVersion())) {
                StdSignBytesV2 stdSignBytesV2 = new StdSignBytesV2(nonce, config.getChainId(), unsignedMsgs, txConfig);
                signDataJson = EncodeUtils.toJsonStringSortKeys(stdSignBytesV2);
            }
            Signature sig = sign(signDataJson, accounts.get(i));
            signatures.add(sig);
        }
        StdTx stdTx = new StdTx(msgs, signatures, txConfig.getGas(), txConfig.getMemo(), nonce);
        return Codec.getAmino().marshalBinaryLengthPrefixed(stdTx);
    }

    public static byte[] buildTx(SysConfig config, Msg msg, Account account, TxConfig txConfig) throws Exception {
        List<Msg> msgs = Arrays.asList(msg);
        List<Account> accounts = Arrays.asList(account);
        return buildTx(config, msgs, accounts, txConfig);
    }


    private static Signature sign(String signDataJson, Account account) throws Exception {
//        signDataJson = encodeStringToUtf8(signDataJson);
//        LOGGER.info("======== 签名转utf8: \n" + signDataJson);
        signDataJson = signDataJson.replaceAll("&", "\\\\u0026");
        signDataJson = signDataJson.replaceAll("<", "\\\\u003c");
        signDataJson = signDataJson.replaceAll(">", "\\\\u003e");
        LOGGER.info("======== 签名前待提交交易的数据: \n" + signDataJson);

        byte[] bytes = signDataJson.getBytes("UTF-8");
        byte[] sig = account.getKeyInfo().sign(bytes);
        return new Signature(account.getKeyInfo().getPubKeyBytes(), sig);
    }

    public static String encodeStringToUtf8(String str) throws Exception {
        String encode = "UTF-8";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "GB2312";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "ISO-8859-1";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "GBK";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "GB18030";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "Big5";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "Unicode";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        encode = "ASCII";
        if (str.equals(new String(str.getBytes(), encode))) {
//            log.info("code type is: " + encode);
            return new String(str.getBytes(encode), "UTF-8");
        }
        return null;
    }


    private static final int NONONCE_LENGTH = 24;

    private static final SecureRandom RANDOM = new SecureRandom();

    public static byte[] generateNonce() {

        byte[] values = new byte[NONONCE_LENGTH];
        RANDOM.nextBytes(values);

        return values;
    }
}
